home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2000 March / maximum-cd-2000-03.iso / Quake3 Game Source / Q3AGameSource.exe / Main / ai_dmnet.c < prev    next >
Encoding:
C/C++ Source or Header  |  2000-01-18  |  57.3 KB  |  1,996 lines

  1. // Copyright (C) 1999-2000 Id Software, Inc.
  2. //
  3.  
  4. /*****************************************************************************
  5.  * name:        ai_dmnet.c
  6.  *
  7.  * desc:        Quake3 bot AI
  8.  *
  9.  * $Archive: /source/code/game/ai_dmnet.c $
  10.  * $Author: Raduffy $ 
  11.  * $Revision: 8 $
  12.  * $Modtime: 1/14/00 5:27p $
  13.  * $Date: 1/14/00 5:35p $
  14.  *
  15.  *****************************************************************************/
  16.  
  17. #include "g_local.h"
  18. #include "botlib.h"
  19. #include "be_aas.h"
  20. #include "be_ea.h"
  21. #include "be_ai_char.h"
  22. #include "be_ai_chat.h"
  23. #include "be_ai_gen.h"
  24. #include "be_ai_goal.h"
  25. #include "be_ai_move.h"
  26. #include "be_ai_weap.h"
  27. //
  28. #include "ai_main.h"
  29. #include "ai_dmq3.h"
  30. #include "ai_chat.h"
  31. #include "ai_cmd.h"
  32. #include "ai_dmnet.h"
  33. //data file headers
  34. #include "chars.h"            //characteristics
  35. #include "inv.h"            //indexes into the inventory
  36. #include "syn.h"            //synonyms
  37. #include "match.h"            //string matching types and vars
  38.  
  39. //goal flag, see be_ai_goal.h for the other GFL_*
  40. #define GFL_AIR            128
  41.  
  42. int numnodeswitches;
  43. char nodeswitch[MAX_NODESWITCHES+1][144];
  44.  
  45. #define LOOKAHEAD_DISTANCE        300
  46.  
  47. /*
  48. ==================
  49. BotResetNodeSwitches
  50. ==================
  51. */
  52. void BotResetNodeSwitches(void) {
  53.     numnodeswitches = 0;
  54. }
  55.  
  56. /*
  57. ==================
  58. BotDumpNodeSwitches
  59. ==================
  60. */
  61. void BotDumpNodeSwitches(bot_state_t *bs) {
  62.     int i;
  63.     char netname[MAX_NETNAME];
  64.  
  65.     ClientName(bs->client, netname, sizeof(netname));
  66.     BotAI_Print(PRT_MESSAGE, "%s at %1.1f switched more than %d AI nodes\n", netname, trap_AAS_Time(), MAX_NODESWITCHES);
  67.     for (i = 0; i < numnodeswitches; i++) {
  68.         BotAI_Print(PRT_MESSAGE, nodeswitch[i]);
  69.     }
  70.     BotAI_Print(PRT_FATAL, "");
  71. }
  72.  
  73. /*
  74. ==================
  75. BotRecordNodeSwitch
  76. ==================
  77. */
  78. void BotRecordNodeSwitch(bot_state_t *bs, char *node, char *str) {
  79.     char netname[MAX_NETNAME];
  80.  
  81.     ClientName(bs->client, netname, sizeof(netname));
  82.     Com_sprintf(nodeswitch[numnodeswitches], 144, "%s at %2.1f entered %s: %s\n", netname, trap_AAS_Time(), node, str);
  83. #ifdef DEBUG
  84.     if (0) {
  85.         BotAI_Print(PRT_MESSAGE, nodeswitch[numnodeswitches]);
  86.     }
  87. #endif //DEBUG
  88.     numnodeswitches++;
  89. }
  90.  
  91. /*
  92. ==================
  93. BotGetAirGoal
  94. ==================
  95. */
  96. int BotGetAirGoal(bot_state_t *bs, bot_goal_t *goal) {
  97.     bsp_trace_t bsptrace;
  98.     vec3_t end, mins = {-15, -15, -2}, maxs = {15, 15, 2};
  99.     int areanum;
  100.  
  101.     //trace up until we hit solid
  102.     VectorCopy(bs->origin, end);
  103.     end[2] += 1000;
  104.     BotAI_Trace(&bsptrace, bs->origin, mins, maxs, end, bs->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
  105.     //trace down until we hit water
  106.     VectorCopy(bsptrace.endpos, end);
  107.     BotAI_Trace(&bsptrace, end, mins, maxs, bs->origin, bs->entitynum, CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA);
  108.     //if we found the water surface
  109.     if (bsptrace.fraction > 0) {
  110.         areanum = BotPointAreaNum(bsptrace.endpos);
  111.         if (areanum) {
  112.             VectorCopy(bsptrace.endpos, goal->origin);
  113.             goal->origin[2] -= 2;
  114.             goal->areanum = areanum;
  115.             goal->mins[0] = -15;
  116.             goal->mins[1] = -15;
  117.             goal->mins[2] = -1;
  118.             goal->maxs[0] = 15;
  119.             goal->maxs[1] = 15;
  120.             goal->maxs[2] = 1;
  121.             goal->flags = GFL_AIR;
  122.             goal->number = 0;
  123.             goal->iteminfo = 0;
  124.             goal->entitynum = 0;
  125.             return qtrue;
  126.         }
  127.     }
  128.     return qfalse;
  129. }
  130.  
  131. /*
  132. ==================
  133. BotGoForAir
  134. ==================
  135. */
  136. int BotGoForAir(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) {
  137.     bot_goal_t goal;
  138.  
  139.     //if the bot needs air
  140.     if (bs->lastair_time < trap_AAS_Time() - 6) {
  141.         //
  142. #ifdef DEBUG
  143.         //BotAI_Print(PRT_MESSAGE, "going for air\n");
  144. #endif //DEBUG
  145.         //if we can find an air goal
  146.         if (BotGetAirGoal(bs, &goal)) {
  147.             trap_BotPushGoal(bs->gs, &goal);
  148.             return qtrue;
  149.         }
  150.         else {
  151.             //get a nearby goal outside the water
  152.             while(trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range)) {
  153.                 trap_BotGetTopGoal(bs->gs, &goal);
  154.                 //if the goal is not in water
  155.                 if (!(trap_AAS_PointContents(goal.origin) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA))) {
  156.                     return qtrue;
  157.                 }
  158.                 trap_BotPopGoal(bs->gs);
  159.             }
  160.             trap_BotResetAvoidGoals(bs->gs);
  161.         }
  162.     }
  163.     return qfalse;
  164. }
  165.  
  166. /*
  167. ==================
  168. BotNearbyGoal
  169. ==================
  170. */
  171. int BotNearbyGoal(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) {
  172.     int ret;
  173.  
  174.     //check if the bot should go for air
  175.     if (BotGoForAir(bs, tfl, ltg, range)) return qtrue;
  176.     //if the bot is carrying the enemy flag
  177.     if (BotCTFCarryingFlag(bs)) {
  178.         //if the bot is just a few secs away from the base 
  179.         if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin,
  180.                 bs->teamgoal.areanum, TFL_DEFAULT) < 300) {
  181.             //make the range really small
  182.             range = 50;
  183.         }
  184.     }
  185.     //
  186.     ret = trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range);
  187.     /*
  188.     if (ret)
  189.     {
  190.         char buf[128];
  191.         //get the goal at the top of the stack
  192.         trap_BotGetTopGoal(bs->gs, &goal);
  193.         trap_BotGoalName(goal.number, buf, sizeof(buf));
  194.         BotAI_Print(PRT_MESSAGE, "%1.1f: new nearby goal %s\n", trap_AAS_Time(), buf);
  195.     }
  196.     //*/
  197.     return ret;
  198. }
  199.  
  200. /*
  201. ==================
  202. BotReachedGoal
  203. ==================
  204. */
  205. int BotReachedGoal(bot_state_t *bs, bot_goal_t *goal) {
  206.     if (goal->flags & GFL_ITEM) {
  207.         //if touching the goal
  208.         if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue;
  209.         //if the goal isn't there
  210.         if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) return qtrue;
  211.         //if in the goal area and below or above the goal and not swimming
  212.         if (bs->areanum == goal->areanum) {
  213.             if (bs->origin[0] > goal->origin[0] + goal->mins[0] && bs->origin[0] < goal->origin[0] + goal->maxs[0]) {
  214.                 if (bs->origin[1] > goal->origin[1] + goal->mins[1] && bs->origin[1] < goal->origin[1] + goal->maxs[1]) {
  215.                     if (!trap_AAS_Swimming(bs->origin)) {
  216.                         return qtrue;
  217.                     }
  218.                 }
  219.             }
  220.         }
  221.     }
  222.     else if (goal->flags & GFL_AIR) {
  223.         //if touching the goal
  224.         if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue;
  225.         //if the bot got air
  226.         if (bs->lastair_time > trap_AAS_Time() - 1) return qtrue;
  227.     }
  228.     else {
  229.         //if touching the goal
  230.         if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue;
  231.     }
  232.     return qfalse;
  233. }
  234.  
  235. /*
  236. ==================
  237. BotGetItemLongTermGoal
  238. ==================
  239. */
  240. int BotGetItemLongTermGoal(bot_state_t *bs, int tfl, bot_goal_t *goal) {
  241.     //if the bot has no goal
  242.     if (!trap_BotGetTopGoal(bs->gs, goal)) {
  243.         //BotAI_Print(PRT_MESSAGE, "no ltg on stack\n");
  244.         bs->ltg_time = 0;
  245.     }
  246.     //if the bot touches the current goal
  247.     else if (BotReachedGoal(bs, goal)) {
  248.         BotChooseWeapon(bs);
  249.         bs->ltg_time = 0;
  250.     }
  251.     //if it is time to find a new long term goal
  252.     if (bs->ltg_time < trap_AAS_Time()) {
  253.         //pop the current goal from the stack
  254.         trap_BotPopGoal(bs->gs);
  255.         //BotAI_Print(PRT_MESSAGE, "%s: choosing new ltg\n", ClientName(bs->client, netname, sizeof(netname)));
  256.         //choose a new goal
  257.         //BotAI_Print(PRT_MESSAGE, "%6.1f client %d: BotChooseLTGItem\n", trap_AAS_Time(), bs->client);
  258.         if (trap_BotChooseLTGItem(bs->gs, bs->origin, bs->inventory, tfl)) {
  259.             /*
  260.             char buf[128];
  261.             //get the goal at the top of the stack
  262.             trap_BotGetTopGoal(bs->gs, goal);
  263.             trap_BotGoalName(goal->number, buf, sizeof(buf));
  264.             BotAI_Print(PRT_MESSAGE, "%1.1f: new long term goal %s\n", trap_AAS_Time(), buf);
  265.             //*/
  266.             bs->ltg_time = trap_AAS_Time() + 20;
  267.         }
  268.         else {//the bot gets sorta stuck with all the avoid timings, shouldn't happen though
  269.             //
  270. #ifdef DEBUG
  271.             char netname[128];
  272.  
  273.             BotAI_Print(PRT_MESSAGE, "%s: no valid ltg (probably stuck)\n", ClientName(bs->client, netname, sizeof(netname)));
  274. #endif
  275.             //trap_BotDumpAvoidGoals(bs->gs);
  276.             //reset the avoid goals and the avoid reach
  277.             trap_BotResetAvoidGoals(bs->gs);
  278.             trap_BotResetAvoidReach(bs->ms);
  279.         }
  280.         //get the goal at the top of the stack
  281.         return trap_BotGetTopGoal(bs->gs, goal);
  282.     }
  283.     return qtrue;
  284. }
  285.  
  286. /*
  287. ==================
  288. BotGetLongTermGoal
  289.  
  290. we could also create a seperate AI node for every long term goal type
  291. however this saves us a lot of code
  292. ==================
  293. */
  294. int BotGetLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) {
  295.     vec3_t target, dir;
  296.     char netname[MAX_NETNAME];
  297.     char buf[MAX_MESSAGE_SIZE];
  298.     int areanum;
  299.     float croucher;
  300.     aas_entityinfo_t entinfo;
  301.     bot_waypoint_t *wp;
  302.  
  303.     if (bs->ltgtype == LTG_TEAMHELP && !retreat) {
  304.         //check for bot typing status message
  305.         if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
  306.             BotAI_BotInitialChat(bs, "help_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
  307.             trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  308.             bs->teammessage_time = 0;
  309.         }
  310.         //if trying to help the team mate for more than a minute
  311.         if (bs->teamgoal_time < trap_AAS_Time())
  312.             bs->ltgtype = 0;
  313.         //if the team mate IS visible for quite some time
  314.         if (bs->teammatevisible_time < trap_AAS_Time() - 10) bs->ltgtype = 0;
  315.         //get entity information of the companion
  316.         BotEntityInfo(bs->teammate, &entinfo);
  317.         //if the team mate is visible
  318.         if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) {
  319.             //if close just stand still there
  320.             VectorSubtract(entinfo.origin, bs->origin, dir);
  321.             if (VectorLength(dir) < 100) {
  322.                 trap_BotResetAvoidReach(bs->ms);
  323.                 return qfalse;
  324.             }
  325.         }
  326.         else {
  327.             //last time the bot was NOT visible
  328.             bs->teammatevisible_time = trap_AAS_Time();
  329.         }
  330.         //if the entity information is valid (entity in PVS)
  331.         if (entinfo.valid) {
  332.             areanum = BotPointAreaNum(entinfo.origin);
  333.             if (areanum && trap_AAS_AreaReachability(areanum)) {
  334.                 //update team goal
  335.                 bs->teamgoal.entitynum = bs->teammate;
  336.                 bs->teamgoal.areanum = areanum;
  337.                 VectorCopy(entinfo.origin, bs->teamgoal.origin);
  338.                 VectorSet(bs->teamgoal.mins, -8, -8, -8);
  339.                 VectorSet(bs->teamgoal.maxs, 8, 8, 8);
  340.             }
  341.         }
  342.         memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
  343.         return qtrue;
  344.     }
  345.     //if the bot accompanies someone
  346.     if (bs->ltgtype == LTG_TEAMACCOMPANY && !retreat) {
  347.         //check for bot typing status message
  348.         if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
  349.             BotAI_BotInitialChat(bs, "accompany_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
  350.             trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  351.             bs->teammessage_time = 0;
  352.         }
  353.         //if accompanying the companion for 3 minutes
  354.         if (bs->teamgoal_time < trap_AAS_Time()) {
  355.             BotAI_BotInitialChat(bs, "accompany_stop", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
  356.             trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  357.             bs->ltgtype = 0;
  358.         }
  359.         //get entity information of the companion
  360.         BotEntityInfo(bs->teammate, &entinfo);
  361.         //if the companion is visible
  362.         if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) {
  363.             //update visible time
  364.             bs->teammatevisible_time = trap_AAS_Time();
  365.             VectorSubtract(entinfo.origin, bs->origin, dir);
  366.             if (VectorLength(dir) < bs->formation_dist) {
  367.                 //check if the bot wants to crouch
  368.                 //don't crouch if crouched less than 5 seconds ago
  369.                 if (bs->attackcrouch_time < trap_AAS_Time() - 5) {
  370.                     croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1);
  371.                     if (random() < bs->thinktime * croucher) {
  372.                         bs->attackcrouch_time = trap_AAS_Time() + 5 + croucher * 15;
  373.                     }
  374.                 }
  375.                 //don't crouch when swimming
  376.                 if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = trap_AAS_Time() - 1;
  377.                 //if not arrived yet or arived some time ago
  378.                 if (bs->arrive_time < trap_AAS_Time() - 2) {
  379.                     //if not arrived yet
  380.                     if (!bs->arrive_time) {
  381.                         trap_EA_Gesture(bs->client);
  382.                         BotAI_BotInitialChat(bs, "accompany_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
  383.                         trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  384.                         bs->arrive_time = trap_AAS_Time();
  385.                     }
  386.                     //if the bot wants to crouch
  387.                     else if (bs->attackcrouch_time > trap_AAS_Time()) {
  388.                         trap_EA_Crouch(bs->client);
  389.                     }
  390.                     //else do some model taunts
  391.                     else if (random() < bs->thinktime * 0.3) {
  392.                         //do a gesture :)
  393.                         trap_EA_Gesture(bs->client);
  394.                     }
  395.                 }
  396.                 //if just arrived look at the companion
  397.                 if (bs->arrive_time > trap_AAS_Time() - 2) {
  398.                     VectorSubtract(entinfo.origin, bs->origin, dir);
  399.                     vectoangles(dir, bs->ideal_viewangles);
  400.                     bs->ideal_viewangles[2] *= 0.5;
  401.                 }
  402.                 //else look strategically around for enemies
  403.                 else if (random() < bs->thinktime * 0.8) {
  404.                     BotRoamGoal(bs, target);
  405.                     VectorSubtract(target, bs->origin, dir);
  406.                     vectoangles(dir, bs->ideal_viewangles);
  407.                     bs->ideal_viewangles[2] *= 0.5;
  408.                 }
  409.                 //check if the bot wants to go for air
  410.                 if (BotGoForAir(bs, bs->tfl, &bs->teamgoal, 400)) {
  411.                     trap_BotResetLastAvoidReach(bs->ms);
  412.                     //get the goal at the top of the stack
  413.                     //trap_BotGetTopGoal(bs->gs, &tmpgoal);
  414.                     //trap_BotGoalName(tmpgoal.number, buf, 144);
  415.                     //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf);
  416.                     //time the bot gets to pick up the nearby goal item
  417.                     bs->nbg_time = trap_AAS_Time() + 8;
  418.                     AIEnter_Seek_NBG(bs);
  419.                     return qfalse;
  420.                 }
  421.                 //
  422.                 trap_BotResetAvoidReach(bs->ms);
  423.                 return qfalse;
  424.             }
  425.         }
  426.         //if the entity information is valid (entity in PVS)
  427.         if (entinfo.valid) {
  428.             areanum = BotPointAreaNum(entinfo.origin);
  429.             if (areanum && trap_AAS_AreaReachability(areanum)) {
  430.                 //update team goal so bot will accompany 
  431.                 bs->teamgoal.entitynum = bs->teammate;
  432.                 bs->teamgoal.areanum = areanum;
  433.                 VectorCopy(entinfo.origin, bs->teamgoal.origin);
  434.                 VectorSet(bs->teamgoal.mins, -8, -8, -8);
  435.                 VectorSet(bs->teamgoal.maxs, 8, 8, 8);
  436.             }
  437.         }
  438.         //the goal the bot should go for
  439.         memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
  440.         //if the companion is NOT visible for too long
  441.         if (bs->teammatevisible_time < trap_AAS_Time() - 60) {
  442.             BotAI_BotInitialChat(bs, "accompany_cannotfind", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
  443.             trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  444.             bs->ltgtype = 0;
  445.         }
  446.         return qtrue;
  447.     }
  448.     //
  449.     if (bs->ltgtype == LTG_DEFENDKEYAREA) {
  450.         if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin,
  451.                 bs->teamgoal.areanum, TFL_DEFAULT) > bs->defendaway_range) {
  452.             bs->defendaway_time = 0;
  453.         }
  454.     }
  455.     //if defending a key area
  456.     if (bs->ltgtype == LTG_DEFENDKEYAREA && !retreat &&
  457.                 bs->defendaway_time < trap_AAS_Time()) {
  458.         //check for bot typing status message
  459.         if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
  460.             trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
  461.             BotAI_BotInitialChat(bs, "defend_start", buf, NULL);
  462.             trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  463.             bs->teammessage_time = 0;
  464.         }
  465.         //set the bot goal
  466.         memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
  467.         //stop after 2 minutes
  468.         if (bs->teamgoal_time < trap_AAS_Time()) {
  469.             trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
  470.             BotAI_BotInitialChat(bs, "defend_stop", buf, NULL);
  471.             trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  472.             bs->ltgtype = 0;
  473.         }
  474.         //if very close... go away for some time
  475.         VectorSubtract(goal->origin, bs->origin, dir);
  476.         if (VectorLength(dir) < 70) {
  477.             trap_BotResetAvoidReach(bs->ms);
  478.             bs->defendaway_time = trap_AAS_Time() + 2 + 5 * random();
  479.             bs->defendaway_range = 250;
  480.         }
  481.         return qtrue;
  482.     }
  483.     //going to kill someone
  484.     if (bs->ltgtype == LTG_KILL && !retreat) {
  485.         //check for bot typing status message
  486.         if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
  487.             EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf));
  488.             BotAI_BotInitialChat(bs, "kill_start", buf, NULL);
  489.             trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  490.             bs->teammessage_time = 0;
  491.         }
  492.         //
  493.         if (bs->lastkilledplayer == bs->teamgoal.entitynum) {
  494.             EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf));
  495.             BotAI_BotInitialChat(bs, "kill_done", buf, NULL);
  496.             trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  497.             bs->lastkilledplayer = -1;
  498.             bs->ltgtype = 0;
  499.         }
  500.         //
  501.         if (bs->teamgoal_time < trap_AAS_Time()) {
  502.             bs->ltgtype = 0;
  503.         }
  504.         //just roam around
  505.         return BotGetItemLongTermGoal(bs, tfl, goal);
  506.     }
  507.     //get an item
  508.     if (bs->ltgtype == LTG_GETITEM && !retreat) {
  509.         //check for bot typing status message
  510.         if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
  511.             trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
  512.             BotAI_BotInitialChat(bs, "getitem_start", buf, NULL);
  513.             trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  514.             bs->teammessage_time = 0;
  515.         }
  516.         //set the bot goal
  517.         memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
  518.         //stop after some time
  519.         if (bs->teamgoal_time < trap_AAS_Time()) {
  520.             bs->ltgtype = 0;
  521.         }
  522.         //
  523.         if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) {
  524.             trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
  525.             BotAI_BotInitialChat(bs, "getitem_notthere", buf, NULL);
  526.             trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  527.             bs->ltgtype = 0;
  528.         }
  529.         else if (BotReachedGoal(bs, goal)) {
  530.             trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
  531.             BotAI_BotInitialChat(bs, "getitem_gotit", buf, NULL);
  532.             trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  533.             bs->ltgtype = 0;
  534.         }
  535.         return qtrue;
  536.     }
  537.     //if camping somewhere
  538.     if ((bs->ltgtype == LTG_CAMP || bs->ltgtype == LTG_CAMPORDER) && !retreat) {
  539.         //check for bot typing status message
  540.         if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
  541.             if (bs->ltgtype == LTG_CAMPORDER) {
  542.                 BotAI_BotInitialChat(bs, "camp_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
  543.                 trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  544.             }
  545.             bs->teammessage_time = 0;
  546.         }
  547.         //set the bot goal
  548.         memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
  549.         //
  550.         if (bs->teamgoal_time < trap_AAS_Time()) {
  551.             if (bs->ltgtype == LTG_CAMPORDER) {
  552.                 BotAI_BotInitialChat(bs, "camp_stop", NULL);
  553.                 trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  554.             }
  555.             bs->ltgtype = 0;
  556.         }
  557.         //if really near the camp spot
  558.         VectorSubtract(goal->origin, bs->origin, dir);
  559.         if (VectorLength(dir) < 60)
  560.         {
  561.             //if not arrived yet
  562.             if (!bs->arrive_time) {
  563.                 if (bs->ltgtype == LTG_CAMPORDER) {
  564.                     BotAI_BotInitialChat(bs, "camp_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
  565.                     trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  566.                 }
  567.                 bs->arrive_time = trap_AAS_Time();
  568.             }
  569.             //look strategically around for enemies
  570.             if (random() < bs->thinktime * 0.8) {
  571.                 BotRoamGoal(bs, target);
  572.                 VectorSubtract(target, bs->origin, dir);
  573.                 vectoangles(dir, bs->ideal_viewangles);
  574.                 bs->ideal_viewangles[2] *= 0.5;
  575.             }
  576.             //check if the bot wants to crouch
  577.             //don't crouch if crouched less than 5 seconds ago
  578.             if (bs->attackcrouch_time < trap_AAS_Time() - 5) {
  579.                 croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1);
  580.                 if (random() < bs->thinktime * croucher) {
  581.                     bs->attackcrouch_time = trap_AAS_Time() + 5 + croucher * 15;
  582.                 }
  583.             }
  584.             //if the bot wants to crouch
  585.             if (bs->attackcrouch_time > trap_AAS_Time()) {
  586.                 trap_EA_Crouch(bs->client);
  587.             }
  588.             //don't crouch when swimming
  589.             if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = trap_AAS_Time() - 1;
  590.             //make sure the bot is not gonna drown
  591.             if (trap_PointContents(bs->eye,bs->entitynum) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) {
  592.                 if (bs->ltgtype == LTG_CAMPORDER) {
  593.                     BotAI_BotInitialChat(bs, "camp_stop", NULL);
  594.                     trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  595.                 }
  596.                 bs->ltgtype = 0;
  597.             }
  598.             //
  599.             if (bs->camp_range > 0) {
  600.                 //FIXME: move around a bit
  601.             }
  602.             //
  603.             trap_BotResetAvoidReach(bs->ms);
  604.             return qfalse;
  605.         }
  606.         return qtrue;
  607.     }
  608.     //patrolling along several waypoints
  609.     if (bs->ltgtype == LTG_PATROL && !retreat) {
  610.         //check for bot typing status message
  611.         if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
  612.             strcpy(buf, "");
  613.             for (wp = bs->patrolpoints; wp; wp = wp->next) {
  614.                 strcat(buf, wp->name);
  615.                 if (wp->next) strcat(buf, " to ");
  616.             }
  617.             BotAI_BotInitialChat(bs, "patrol_start", buf, NULL);
  618.             trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  619.             bs->teammessage_time = 0;
  620.         }
  621.         //
  622.         if (!bs->curpatrolpoint) {
  623.             bs->ltgtype = 0;
  624.             return qfalse;
  625.         }
  626.         //if the bot touches the current goal
  627.         if (trap_BotTouchingGoal(bs->origin, &bs->curpatrolpoint->goal)) {
  628.             if (bs->patrolflags & PATROL_BACK) {
  629.                 if (bs->curpatrolpoint->prev) {
  630.                     bs->curpatrolpoint = bs->curpatrolpoint->prev;
  631.                 }
  632.                 else {
  633.                     bs->curpatrolpoint = bs->curpatrolpoint->next;
  634.                     bs->patrolflags &= ~PATROL_BACK;
  635.                 }
  636.             }
  637.             else {
  638.                 if (bs->curpatrolpoint->next) {
  639.                     bs->curpatrolpoint = bs->curpatrolpoint->next;
  640.                 }
  641.                 else {
  642.                     bs->curpatrolpoint = bs->curpatrolpoint->prev;
  643.                     bs->patrolflags |= PATROL_BACK;
  644.                 }
  645.             }
  646.         }
  647.         //stop after 5 minutes
  648.         if (bs->teamgoal_time < trap_AAS_Time()) {
  649.             BotAI_BotInitialChat(bs, "patrol_stop", NULL);
  650.             trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  651.             bs->ltgtype = 0;
  652.         }
  653.         if (!bs->curpatrolpoint) {
  654.             bs->ltgtype = 0;
  655.             return qfalse;
  656.         }
  657.         memcpy(goal, &bs->curpatrolpoint->goal, sizeof(bot_goal_t));
  658.         return qtrue;
  659.     }
  660. #ifdef CTF
  661.     //if going for enemy flag
  662.     if (bs->ltgtype == LTG_GETFLAG) {
  663.         //check for bot typing status message
  664.         if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
  665.             BotAI_BotInitialChat(bs, "captureflag_start", NULL);
  666.             trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  667.             bs->teammessage_time = 0;
  668.         }
  669.         //
  670.         switch(BotCTFTeam(bs)) {
  671.             case CTF_TEAM_RED: *goal = ctf_blueflag; break;
  672.             case CTF_TEAM_BLUE: *goal = ctf_redflag; break;
  673.             default: bs->ltgtype = 0; return qfalse;
  674.         }
  675.         //if touching the flag
  676.         if (trap_BotTouchingGoal(bs->origin, goal)) bs->ltgtype = 0;
  677.         //stop after 3 minutes
  678.         if (bs->teamgoal_time < trap_AAS_Time()) {
  679. #ifdef DEBUG
  680.             BotAI_Print(PRT_MESSAGE, "%s: I quit getting the flag\n", ClientName(bs->client, netname, sizeof(netname)));
  681. #endif //DEBUG
  682.             bs->ltgtype = 0;
  683.         }
  684.         return qtrue;
  685.     }
  686.     //if rushing to the base
  687.     if (bs->ltgtype == LTG_RUSHBASE && bs->rushbaseaway_time < trap_AAS_Time()) {
  688.         switch(BotCTFTeam(bs)) {
  689.             case CTF_TEAM_RED: *goal = ctf_redflag; break;
  690.             case CTF_TEAM_BLUE: *goal = ctf_blueflag; break;
  691.             default: bs->ltgtype = 0; return qfalse;
  692.         }
  693.         //if not carrying the flag anymore
  694.         if (!BotCTFCarryingFlag(bs)) bs->ltgtype = 0;
  695.         //quit rushing after 2 minutes
  696.         if (bs->teamgoal_time < trap_AAS_Time()) bs->ltgtype = 0;
  697.         //if touching the base flag the bot should loose the enemy flag
  698.         if (trap_BotTouchingGoal(bs->origin, goal)) {
  699.             //if the bot is still carrying the enemy flag then the
  700.             //base flag is gone, now just walk near the base a bit
  701.             if (BotCTFCarryingFlag(bs)) {
  702.                 trap_BotResetAvoidReach(bs->ms);
  703.                 bs->rushbaseaway_time = trap_AAS_Time() + 5 + 10 * random();
  704.                 //FIXME: add chat to tell the others to get back the flag
  705.             }
  706.             else {
  707.                 bs->ltgtype = 0;
  708.             }
  709.         }
  710.         return qtrue;
  711.     }
  712.     //returning flag
  713.     if (bs->ltgtype == LTG_RETURNFLAG) {
  714.         //check for bot typing status message
  715.         if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
  716.             EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf));
  717.             BotAI_BotInitialChat(bs, "returnflag_start", buf, NULL);
  718.             trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  719.             bs->teammessage_time = 0;
  720.         }
  721.         //
  722.         if (bs->teamgoal_time < trap_AAS_Time()) {
  723.             bs->ltgtype = 0;
  724.         }
  725.         //just roam around
  726.         return BotGetItemLongTermGoal(bs, tfl, goal);
  727.     }
  728. #endif //CTF
  729.     //normal goal stuff
  730.     return BotGetItemLongTermGoal(bs, tfl, goal);
  731. }
  732.  
  733. /*
  734. ==================
  735. BotLongTermGoal
  736. ==================
  737. */
  738. int BotLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) {
  739.     aas_entityinfo_t entinfo;
  740.     char teammate[MAX_MESSAGE_SIZE];
  741.     float dist;
  742.     int areanum;
  743.     vec3_t dir;
  744.  
  745.     //FIXME: also have air long term goals?
  746.     //
  747.     //if the bot is leading someone and not retreating
  748.     if (bs->lead_time > 0 && !retreat) {
  749.         if (bs->lead_time < trap_AAS_Time()) {
  750.             //FIXME: add chat to tell the team mate that he/she's on his/her own
  751.             bs->lead_time = 0;
  752.             return BotGetLongTermGoal(bs, tfl, retreat, goal);
  753.         }
  754.         //
  755.         if (bs->leadmessage_time < 0 && -bs->leadmessage_time < trap_AAS_Time()) {
  756.             BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL);
  757.             trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  758.             bs->leadmessage_time = trap_AAS_Time();
  759.         }
  760.         //get entity information of the companion
  761.         BotEntityInfo(bs->lead_teammate, &entinfo);
  762.         //
  763.         if (entinfo.valid) {
  764.             areanum = BotPointAreaNum(entinfo.origin);
  765.             if (areanum && trap_AAS_AreaReachability(areanum)) {
  766.                 //update team goal
  767.                 bs->lead_teamgoal.entitynum = bs->lead_teammate;
  768.                 bs->lead_teamgoal.areanum = areanum;
  769.                 VectorCopy(entinfo.origin, bs->lead_teamgoal.origin);
  770.                 VectorSet(bs->lead_teamgoal.mins, -8, -8, -8);
  771.                 VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8);
  772.             }
  773.         }
  774.         //if the team mate is visible
  775.         if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->lead_teammate)) {
  776.             bs->leadvisible_time = trap_AAS_Time();
  777.         }
  778.         //if the team mate is not visible for 1 seconds
  779.         if (bs->leadvisible_time < trap_AAS_Time() - 1) {
  780.             bs->leadbackup_time = trap_AAS_Time() + 2;
  781.         }
  782.         //distance towards the team mate
  783.         VectorSubtract(bs->origin, bs->lead_teamgoal.origin, dir);
  784.         dist = VectorLength(dir);
  785.         //if backing up towards the team mate
  786.         if (bs->leadbackup_time > trap_AAS_Time()) {
  787.             if (bs->leadmessage_time < trap_AAS_Time() - 20) {
  788.                 BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL);
  789.                 trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  790.                 bs->leadmessage_time = trap_AAS_Time();
  791.             }
  792.             //if very close to the team mate
  793.             if (dist < 100) {
  794.                 bs->leadbackup_time = 0;
  795.             }
  796.             //the bot should go back to the team mate
  797.             memcpy(goal, &bs->lead_teamgoal, sizeof(bot_goal_t));
  798.             return qtrue;
  799.         }
  800.         else {
  801.             //if quite distant from the team mate
  802.             if (dist > 500) {
  803.                 if (bs->leadmessage_time < trap_AAS_Time() - 20) {
  804.                     BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL);
  805.                     trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
  806.                     bs->leadmessage_time = trap_AAS_Time();
  807.                 }
  808.                 //look at the team mate
  809.                 VectorSubtract(entinfo.origin, bs->origin, dir);
  810.                 vectoangles(dir, bs->ideal_viewangles);
  811.                 bs->ideal_viewangles[2] *= 0.5;
  812.                 //just wait for the team mate
  813.                 return qfalse;
  814.             }
  815.         }
  816.     }
  817.     return BotGetLongTermGoal(bs, tfl, retreat, goal);
  818. }
  819.  
  820. /*
  821. ==================
  822. AIEnter_Intermission
  823. ==================
  824. */
  825. void AIEnter_Intermission(bot_state_t *bs) {
  826.     BotRecordNodeSwitch(bs, "intermission", "");
  827.     //reset the bot state
  828.     BotResetState(bs);
  829.     //check for end level chat
  830.     if (BotChat_EndLevel(bs)) {
  831.         trap_BotEnterChat(bs->cs, bs->client, bs->chatto);
  832.     }
  833.     bs->ainode = AINode_Intermission;
  834. }
  835.  
  836. /*
  837. ==================
  838. AINode_Intermission
  839. ==================
  840. */
  841. int AINode_Intermission(bot_state_t *bs) {
  842.     //if the intermission ended
  843.     if (!BotIntermission(bs)) {
  844.         if (BotChat_StartLevel(bs)) {
  845.             bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
  846.         }
  847.         else {
  848.             bs->stand_time = trap_AAS_Time() + 2;
  849.         }
  850.         AIEnter_Stand(bs);
  851.     }
  852.     return qtrue;
  853. }
  854.  
  855. /*
  856. ==================
  857. AIEnter_Observer
  858. ==================
  859. */
  860. void AIEnter_Observer(bot_state_t *bs) {
  861.     BotRecordNodeSwitch(bs, "observer", "");
  862.     //reset the bot state
  863.     BotResetState(bs);
  864.     bs->ainode = AINode_Observer;
  865. }
  866.  
  867. /*
  868. ==================
  869. AINode_Observer
  870. ==================
  871. */
  872. int AINode_Observer(bot_state_t *bs) {
  873.     //if the bot left observer mode
  874.     if (!BotIsObserver(bs)) {
  875.         AIEnter_Stand(bs);
  876.     }
  877.     return qtrue;
  878. }
  879.  
  880. /*
  881. ==================
  882. AIEnter_Stand
  883. ==================
  884. */
  885. void AIEnter_Stand(bot_state_t *bs) {
  886.     BotRecordNodeSwitch(bs, "stand", "");
  887.     bs->standfindenemy_time = trap_AAS_Time() + 1;
  888.     bs->ainode = AINode_Stand;
  889. }
  890.  
  891. /*
  892. ==================
  893. AINode_Stand
  894. ==================
  895. */
  896. int AINode_Stand(bot_state_t *bs) {
  897.  
  898.     //if the bot's health decreased
  899.     if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) {
  900.         if (BotChat_HitTalking(bs)) {
  901.             bs->standfindenemy_time = trap_AAS_Time() + BotChatTime(bs) + 0.1;
  902.             bs->stand_time = trap_AAS_Time() + BotChatTime(bs) + 0.1;
  903.         }
  904.     }
  905.     if (bs->standfindenemy_time < trap_AAS_Time()) {
  906.         if (BotFindEnemy(bs, -1)) {
  907.             AIEnter_Battle_Fight(bs);
  908.             return qfalse;
  909.         }
  910.         bs->standfindenemy_time = trap_AAS_Time() + 1;
  911.     }
  912.     trap_EA_Talk(bs->client);
  913.     if (bs->stand_time < trap_AAS_Time()) {
  914.         trap_BotEnterChat(bs->cs, bs->client, bs->chatto);
  915.         AIEnter_Seek_LTG(bs);
  916.         return qfalse;
  917.     }
  918.     //
  919.     return qtrue;
  920. }
  921.  
  922. /*
  923. ==================
  924. AIEnter_Respawn
  925. ==================
  926. */
  927. void AIEnter_Respawn(bot_state_t *bs) {
  928.     BotRecordNodeSwitch(bs, "respawn", "");
  929.     //reset some states
  930.     trap_BotResetMoveState(bs->ms);
  931.     trap_BotResetGoalState(bs->gs);
  932.     trap_BotResetAvoidGoals(bs->gs);
  933.     trap_BotResetAvoidReach(bs->ms);
  934.     //if the bot wants to chat
  935.     if (BotChat_Death(bs)) {
  936.         bs->respawn_time = trap_AAS_Time() + BotChatTime(bs);
  937.         bs->respawnchat_time = trap_AAS_Time();
  938.     }
  939.     else {
  940.         bs->respawn_time = trap_AAS_Time() + 1 + random();
  941.         bs->respawnchat_time = 0;
  942.     }
  943.     //set respawn state
  944.     bs->respawn_wait = qfalse;
  945.     bs->ainode = AINode_Respawn;
  946. }
  947.  
  948. /*
  949. ==================
  950. AINode_Respawn
  951. ==================
  952. */
  953. int AINode_Respawn(bot_state_t *bs) {
  954.     if (bs->respawn_wait) {
  955.         if (!BotIsDead(bs)) {
  956.             AIEnter_Seek_LTG(bs);
  957.         }
  958.         else {
  959.             trap_EA_Respawn(bs->client);
  960.         }
  961.     }
  962.     else if (bs->respawn_time < trap_AAS_Time()) {
  963.         //wait until respawned
  964.         bs->respawn_wait = qtrue;
  965.         //elementary action respawn
  966.         trap_EA_Respawn(bs->client);
  967.         //
  968.         if (bs->respawnchat_time) {
  969.             trap_BotEnterChat(bs->cs, bs->client, bs->chatto);
  970.             bs->enemy = -1;
  971.         }
  972.     }
  973.     if (bs->respawnchat_time && bs->respawnchat_time < trap_AAS_Time() - 0.5) {
  974.         trap_EA_Talk(bs->client);
  975.     }
  976.     //
  977.     return qtrue;
  978. }
  979.  
  980. /*
  981. ==================
  982. AIEnter_Seek_ActivateEntity
  983. ==================
  984. */
  985. void AIEnter_Seek_ActivateEntity(bot_state_t *bs) {
  986.     BotRecordNodeSwitch(bs, "activate entity", "");
  987.     bs->ainode = AINode_Seek_ActivateEntity;
  988. }
  989.  
  990. /*
  991. ==================
  992. AINode_Seek_Activate_Entity
  993. ==================
  994. */
  995. int AINode_Seek_ActivateEntity(bot_state_t *bs) {
  996.     bot_goal_t *goal;
  997.     vec3_t target, dir;
  998.     bot_moveresult_t moveresult;
  999.  
  1000.     if (BotIsObserver(bs)) {
  1001.         AIEnter_Observer(bs);
  1002.         return qfalse;
  1003.     }
  1004.     //if in the intermission
  1005.     if (BotIntermission(bs)) {
  1006.         AIEnter_Intermission(bs);
  1007.         return qfalse;
  1008.     }
  1009.     //respawn if dead
  1010.     if (BotIsDead(bs)) {
  1011.         AIEnter_Respawn(bs);
  1012.         return qfalse;
  1013.     }
  1014.     //
  1015.     bs->tfl = TFL_DEFAULT;
  1016.     if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
  1017.     //if in lava or slime the bot should be able to get out
  1018.     if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
  1019.     //map specific code
  1020.     BotMapScripts(bs);
  1021.     //no enemy
  1022.     bs->enemy = -1;
  1023.     //
  1024.     goal = &bs->activategoal;
  1025.     //if the bot has no goal
  1026.     if (!goal) bs->activate_time = 0;
  1027.     //if the bot touches the current goal
  1028.     else if (trap_BotTouchingGoal(bs->origin, goal)) {
  1029.         BotChooseWeapon(bs);
  1030. #ifdef DEBUG
  1031.         BotAI_Print(PRT_MESSAGE, "touched button or trigger\n");
  1032. #endif //DEBUG
  1033.         bs->activate_time = 0;
  1034.     }
  1035.     //
  1036.     if (bs->activate_time < trap_AAS_Time()) {
  1037.         AIEnter_Seek_NBG(bs);
  1038.         return qfalse;
  1039.     }
  1040.     //initialize the movement state
  1041.     BotSetupForMovement(bs);
  1042.     //move towards the goal
  1043.     trap_BotMoveToGoal(&moveresult, bs->ms, goal, bs->tfl);
  1044.     //if the movement failed
  1045.     if (moveresult.failure) {
  1046.         //reset the avoid reach, otherwise bot is stuck in current area
  1047.         trap_BotResetAvoidReach(bs->ms);
  1048.         bs->nbg_time = 0;
  1049.     }
  1050.     //check if the bot is blocked
  1051.     BotAIBlocked(bs, &moveresult, qtrue);
  1052.     //
  1053.     if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
  1054.         VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
  1055.     }
  1056.     //if waiting for something
  1057.     else if (moveresult.flags & MOVERESULT_WAITING) {
  1058.         if (random() < bs->thinktime * 0.8) {
  1059.             BotRoamGoal(bs, target);
  1060.             VectorSubtract(target, bs->origin, dir);
  1061.             vectoangles(dir, bs->ideal_viewangles);
  1062.             bs->ideal_viewangles[2] *= 0.5;
  1063.         }
  1064.     }
  1065.     else if (!(bs->flags & BFL_IDEALVIEWSET)) {
  1066.         if (trap_BotMovementViewTarget(bs->ms, goal, bs->tfl, 300, target)) {
  1067.             VectorSubtract(target, bs->origin, dir);
  1068.             vectoangles(dir, bs->ideal_viewangles);
  1069.         }
  1070.         else {
  1071.             //vectoangles(moveresult.movedir, bs->ideal_viewangles);
  1072.         }
  1073.         bs->ideal_viewangles[2] *= 0.5;
  1074.     }
  1075.     //if the weapon is used for the bot movement
  1076.     if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
  1077.     //if there is an enemy
  1078.     if (BotFindEnemy(bs, -1)) {
  1079.         if (BotWantsToRetreat(bs)) {
  1080.             //keep the current long term goal and retreat
  1081.             AIEnter_Battle_NBG(bs);
  1082.         }
  1083.         else {
  1084.             trap_BotResetLastAvoidReach(bs->ms);
  1085.             //empty the goal stack
  1086.             trap_BotEmptyGoalStack(bs->gs);
  1087.             //go fight
  1088.             AIEnter_Battle_Fight(bs);
  1089.         }
  1090.     }
  1091.     return qtrue;
  1092. }
  1093.  
  1094. /*
  1095. ==================
  1096. AIEnter_Seek_NBG
  1097. ==================
  1098. */
  1099. void AIEnter_Seek_NBG(bot_state_t *bs) {
  1100.     bot_goal_t goal;
  1101.     char buf[144];
  1102.  
  1103.     if (trap_BotGetTopGoal(bs->gs, &goal)) {
  1104.         trap_BotGoalName(goal.number, buf, 144);
  1105.         BotRecordNodeSwitch(bs, "seek NBG", buf);
  1106.     }
  1107.     else {
  1108.         BotRecordNodeSwitch(bs, "seek NBG", "no goal");
  1109.     }
  1110.     bs->ainode = AINode_Seek_NBG;
  1111. }
  1112.  
  1113. /*
  1114. ==================
  1115. AINode_Seek_NBG
  1116. ==================
  1117. */
  1118. int AINode_Seek_NBG(bot_state_t *bs) {
  1119.     bot_goal_t goal;
  1120.     vec3_t target, dir;
  1121.     bot_moveresult_t moveresult;
  1122.  
  1123.     if (BotIsObserver(bs)) {
  1124.         AIEnter_Observer(bs);
  1125.         return qfalse;
  1126.     }
  1127.     //if in the intermission
  1128.     if (BotIntermission(bs)) {
  1129.         AIEnter_Intermission(bs);
  1130.         return qfalse;
  1131.     }
  1132.     //respawn if dead
  1133.     if (BotIsDead(bs)) {
  1134.         AIEnter_Respawn(bs);
  1135.         return qfalse;
  1136.     }
  1137.     //
  1138.     bs->tfl = TFL_DEFAULT;
  1139.     if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
  1140.     //if in lava or slime the bot should be able to get out
  1141.     if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
  1142.     //
  1143.     if (BotCanAndWantsToRocketJump(bs)) {
  1144.         bs->tfl |= TFL_ROCKETJUMP;
  1145.     }
  1146.     //map specific code
  1147.     BotMapScripts(bs);
  1148.     //no enemy
  1149.     bs->enemy = -1;
  1150.     //if the bot has no goal
  1151.     if (!trap_BotGetTopGoal(bs->gs, &goal)) bs->nbg_time = 0;
  1152.     //if the bot touches the current goal
  1153.     else if (BotReachedGoal(bs, &goal)) {
  1154.         BotChooseWeapon(bs);
  1155.         bs->nbg_time = 0;
  1156.     }
  1157.     //
  1158.     if (bs->nbg_time < trap_AAS_Time()) {
  1159.         //pop the current goal from the stack
  1160.         trap_BotPopGoal(bs->gs);
  1161.         //check for new nearby items right away
  1162.         //NOTE: we canNOT reset the check_time to zero because it would create an endless loop of node switches
  1163.         bs->check_time = trap_AAS_Time() + 0.05;
  1164.         //go back to seek ltg
  1165.         AIEnter_Seek_LTG(bs);
  1166.         return qfalse;
  1167.     }
  1168.     //initialize the movement state
  1169.     BotSetupForMovement(bs);
  1170.     //move towards the goal
  1171.     trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
  1172.     //if the movement failed
  1173.     if (moveresult.failure) {
  1174.         //reset the avoid reach, otherwise bot is stuck in current area
  1175.         trap_BotResetAvoidReach(bs->ms);
  1176.         bs->nbg_time = 0;
  1177.     }
  1178.     //check if the bot is blocked
  1179.     BotAIBlocked(bs, &moveresult, qtrue);
  1180.     //if the viewangles are used for the movement
  1181.     if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
  1182.         VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
  1183.     }
  1184.     //if waiting for something
  1185.     else if (moveresult.flags & MOVERESULT_WAITING) {
  1186.         if (random() < bs->thinktime * 0.8) {
  1187.             BotRoamGoal(bs, target);
  1188.             VectorSubtract(target, bs->origin, dir);
  1189.             vectoangles(dir, bs->ideal_viewangles);
  1190.             bs->ideal_viewangles[2] *= 0.5;
  1191.         }
  1192.     }
  1193.     else if (!(bs->flags & BFL_IDEALVIEWSET)) {
  1194.         if (!trap_BotGetSecondGoal(bs->gs, &goal)) trap_BotGetTopGoal(bs->gs, &goal);
  1195.         if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
  1196.             VectorSubtract(target, bs->origin, dir);
  1197.             vectoangles(dir, bs->ideal_viewangles);
  1198.         }
  1199.         //FIXME: look at cluster portals?
  1200.         else vectoangles(moveresult.movedir, bs->ideal_viewangles);
  1201.         bs->ideal_viewangles[2] *= 0.5;
  1202.     }
  1203.     //if the weapon is used for the bot movement
  1204.     if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
  1205.     //if there is an enemy
  1206.     if (BotFindEnemy(bs, -1)) {
  1207.         if (BotWantsToRetreat(bs)) {
  1208.             //keep the current long term goal and retreat
  1209.             AIEnter_Battle_NBG(bs);
  1210.         }
  1211.         else {
  1212.             trap_BotResetLastAvoidReach(bs->ms);
  1213.             //empty the goal stack
  1214.             trap_BotEmptyGoalStack(bs->gs);
  1215.             //go fight
  1216.             AIEnter_Battle_Fight(bs);
  1217.         }
  1218.     }
  1219.     return qtrue;
  1220. }
  1221.  
  1222. /*
  1223. ==================
  1224. AIEnter_Seek_LTG
  1225. ==================
  1226. */
  1227. void AIEnter_Seek_LTG(bot_state_t *bs) {
  1228.     bot_goal_t goal;
  1229.     char buf[144];
  1230.  
  1231.     if (trap_BotGetTopGoal(bs->gs, &goal)) {
  1232.         trap_BotGoalName(goal.number, buf, 144);
  1233.         BotRecordNodeSwitch(bs, "seek LTG", buf);
  1234.     }
  1235.     else {
  1236.         BotRecordNodeSwitch(bs, "seek LTG", "no goal");
  1237.     }
  1238.     bs->ainode = AINode_Seek_LTG;
  1239. }
  1240.  
  1241. /*
  1242. ==================
  1243. AINode_Seek_LTG
  1244. ==================
  1245. */
  1246. int AINode_Seek_LTG(bot_state_t *bs)
  1247. {
  1248.     bot_goal_t goal;
  1249.     vec3_t target, dir;
  1250.     bot_moveresult_t moveresult;
  1251.     int range;
  1252.     //char buf[128];
  1253.     //bot_goal_t tmpgoal;
  1254.  
  1255.     if (BotIsObserver(bs)) {
  1256.         AIEnter_Observer(bs);
  1257.         return qfalse;
  1258.     }
  1259.     //if in the intermission
  1260.     if (BotIntermission(bs)) {
  1261.         AIEnter_Intermission(bs);
  1262.         return qfalse;
  1263.     }
  1264.     //respawn if dead
  1265.     if (BotIsDead(bs)) {
  1266.         AIEnter_Respawn(bs);
  1267.         return qfalse;
  1268.     }
  1269.     //
  1270.     if (BotChat_Random(bs)) {
  1271.         bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
  1272.         AIEnter_Stand(bs);
  1273.         return qfalse;
  1274.     }
  1275.     //
  1276.     bs->tfl = TFL_DEFAULT;
  1277.     if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
  1278.     //if in lava or slime the bot should be able to get out
  1279.     if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
  1280.     //
  1281.     if (BotCanAndWantsToRocketJump(bs)) {
  1282.         bs->tfl |= TFL_ROCKETJUMP;
  1283.     }
  1284.     //map specific code
  1285.     BotMapScripts(bs);
  1286.     //no enemy
  1287.     bs->enemy = -1;
  1288.     //
  1289.     if (bs->killedenemy_time > trap_AAS_Time() - 2) {
  1290.         if (random() < bs->thinktime * 1) {
  1291.             trap_EA_Gesture(bs->client);
  1292.         }
  1293.     }
  1294.     //if there is an enemy
  1295.     if (BotFindEnemy(bs, -1)) {
  1296.         if (BotWantsToRetreat(bs)) {
  1297.             //keep the current long term goal and retreat
  1298.             AIEnter_Battle_Retreat(bs);
  1299.             return qfalse;
  1300.         }
  1301.         else {
  1302.             trap_BotResetLastAvoidReach(bs->ms);
  1303.             //empty the goal stack
  1304.             trap_BotEmptyGoalStack(bs->gs);
  1305.             //go fight
  1306.             AIEnter_Battle_Fight(bs);
  1307.             return qfalse;
  1308.         }
  1309.     }
  1310. #ifdef CTF
  1311.     if (gametype == GT_CTF) {
  1312.         //decide what to do in CTF mode
  1313.         BotCTFSeekGoals(bs);
  1314.     }
  1315. #endif //CTF
  1316.     //get the current long term goal
  1317.     if (!BotLongTermGoal(bs, bs->tfl, qfalse, &goal)) {
  1318.         return qtrue;
  1319.     }
  1320.     //check for nearby goals periodicly
  1321.     if (bs->check_time < trap_AAS_Time()) {
  1322.         bs->check_time = trap_AAS_Time() + 0.5;
  1323.         //check if the bot wants to camp
  1324.         BotWantsToCamp(bs);
  1325.         //
  1326.         if (bs->ltgtype == LTG_DEFENDKEYAREA) range = 400;
  1327.         else range = 150;
  1328.         //
  1329. #ifdef CTF
  1330.         //if carrying a flag the bot shouldn't be distracted too much
  1331.         if (BotCTFCarryingFlag(bs)) range = 50;
  1332. #endif //CTF
  1333.         //
  1334.         if (BotNearbyGoal(bs, bs->tfl, &goal, range)) {
  1335.             trap_BotResetLastAvoidReach(bs->ms);
  1336.             //get the goal at the top of the stack
  1337.             //trap_BotGetTopGoal(bs->gs, &tmpgoal);
  1338.             //trap_BotGoalName(tmpgoal.number, buf, 144);
  1339.             //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf);
  1340.             //time the bot gets to pick up the nearby goal item
  1341.             bs->nbg_time = trap_AAS_Time() + 4 + range * 0.01;
  1342.             AIEnter_Seek_NBG(bs);
  1343.             return qfalse;
  1344.         }
  1345.     }
  1346.     //initialize the movement state
  1347.     BotSetupForMovement(bs);
  1348.     //move towards the goal
  1349.     trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
  1350.     //if the movement failed
  1351.     if (moveresult.failure) {
  1352.         //reset the avoid reach, otherwise bot is stuck in current area
  1353.         trap_BotResetAvoidReach(bs->ms);
  1354.         //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
  1355.         bs->ltg_time = 0;
  1356.     }
  1357.     //
  1358.     BotAIBlocked(bs, &moveresult, qtrue);
  1359.     //if the viewangles are used for the movement
  1360.     if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
  1361.         VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
  1362.     }
  1363.     //if waiting for something
  1364.     else if (moveresult.flags & MOVERESULT_WAITING) {
  1365.         if (random() < bs->thinktime * 0.8) {
  1366.             BotRoamGoal(bs, target);
  1367.             VectorSubtract(target, bs->origin, dir);
  1368.             vectoangles(dir, bs->ideal_viewangles);
  1369.             bs->ideal_viewangles[2] *= 0.5;
  1370.         }
  1371.     }
  1372.     else if (!(bs->flags & BFL_IDEALVIEWSET)) {
  1373.         if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
  1374.             VectorSubtract(target, bs->origin, dir);
  1375.             vectoangles(dir, bs->ideal_viewangles);
  1376.         }
  1377.         //FIXME: look at cluster portals?
  1378.         else if (VectorLength(moveresult.movedir)) {
  1379.             vectoangles(moveresult.movedir, bs->ideal_viewangles);
  1380.         }
  1381.         else if (random() < bs->thinktime * 0.8) {
  1382.             BotRoamGoal(bs, target);
  1383.             VectorSubtract(target, bs->origin, dir);
  1384.             vectoangles(dir, bs->ideal_viewangles);
  1385.             bs->ideal_viewangles[2] *= 0.5;
  1386.         }
  1387.         bs->ideal_viewangles[2] *= 0.5;
  1388.     }
  1389.     //if the weapon is used for the bot movement
  1390.     if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
  1391.     //
  1392.     return qtrue;
  1393. }
  1394.  
  1395. /*
  1396. ==================
  1397. AIEnter_Battle_Fight
  1398. ==================
  1399. */
  1400. void AIEnter_Battle_Fight(bot_state_t *bs) {
  1401.     BotRecordNodeSwitch(bs, "battle fight", "");
  1402.     trap_BotResetLastAvoidReach(bs->ms);
  1403.     bs->ainode = AINode_Battle_Fight;
  1404. }
  1405.  
  1406. /*
  1407. ==================
  1408. AIEnter_Battle_Fight
  1409. ==================
  1410. */
  1411. void AIEnter_Battle_SuicidalFight(bot_state_t *bs) {
  1412.     BotRecordNodeSwitch(bs, "battle fight", "");
  1413.     trap_BotResetLastAvoidReach(bs->ms);
  1414.     bs->ainode = AINode_Battle_Fight;
  1415.     bs->flags |= BFL_FIGHTSUICIDAL;
  1416. }
  1417.  
  1418. /*
  1419. ==================
  1420. AINode_Battle_Fight
  1421. ==================
  1422. */
  1423. int AINode_Battle_Fight(bot_state_t *bs) {
  1424.     int areanum;
  1425.     aas_entityinfo_t entinfo;
  1426.     bot_moveresult_t moveresult;
  1427.  
  1428.     if (BotIsObserver(bs)) {
  1429.         AIEnter_Observer(bs);
  1430.         return qfalse;
  1431.     }
  1432.  
  1433.     //if in the intermission
  1434.     if (BotIntermission(bs)) {
  1435.         AIEnter_Intermission(bs);
  1436.         return qfalse;
  1437.     }
  1438.     //respawn if dead
  1439.     if (BotIsDead(bs)) {
  1440.         AIEnter_Respawn(bs);
  1441.         return qfalse;
  1442.     }
  1443.     //if there is another better enemy
  1444.     if (BotFindEnemy(bs, bs->enemy)) {
  1445. #ifdef DEBUG
  1446.         BotAI_Print(PRT_MESSAGE, "found new better enemy\n");
  1447. #endif
  1448.     }
  1449.     //if no enemy
  1450.     if (bs->enemy < 0) {
  1451.         AIEnter_Seek_LTG(bs);
  1452.         return qfalse;
  1453.     }
  1454.     //
  1455.     BotEntityInfo(bs->enemy, &entinfo);
  1456.     //if the enemy is dead
  1457.     if (bs->enemydeath_time) {
  1458.         if (bs->enemydeath_time < trap_AAS_Time() - 1.0) {
  1459.             bs->enemydeath_time = 0;
  1460.             if (bs->enemysuicide) {
  1461.                 BotChat_EnemySuicide(bs);
  1462.             }
  1463.             if (bs->lastkilledplayer == bs->enemy && BotChat_Kill(bs)) {
  1464.                 bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
  1465.                 AIEnter_Stand(bs);
  1466.             }
  1467.             else {
  1468.                 bs->ltg_time = 0;
  1469.                 AIEnter_Seek_LTG(bs);
  1470.             }
  1471.             return qfalse;
  1472.         }
  1473.     }
  1474.     else {
  1475.         if (EntityIsDead(&entinfo)) {
  1476.             bs->enemydeath_time = trap_AAS_Time();
  1477.         }
  1478.     }
  1479.     //if the enemy is invisible and not shooting the bot looses track easily
  1480.     if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) {
  1481.         if (random() < 0.2) {
  1482.             AIEnter_Seek_LTG(bs);
  1483.             return qfalse;
  1484.         }
  1485.     }
  1486.     //update the reachability area and origin if possible
  1487.     areanum = BotPointAreaNum(entinfo.origin);
  1488.     if (areanum && trap_AAS_AreaReachability(areanum)) {
  1489.         VectorCopy(entinfo.origin, bs->lastenemyorigin);
  1490.         bs->lastenemyareanum = areanum;
  1491.     }
  1492.     //update the attack inventory values
  1493.     BotUpdateBattleInventory(bs, bs->enemy);
  1494.     //if the bot's health decreased
  1495.     if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) {
  1496.         if (BotChat_HitNoDeath(bs)) {
  1497.             bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
  1498.             AIEnter_Stand(bs);
  1499.             return qfalse;
  1500.         }
  1501.     }
  1502.     //if the bot hit someone
  1503.     if (bs->cur_ps.persistant[PERS_HITS] > bs->lasthitcount) {
  1504.         if (BotChat_HitNoKill(bs)) {
  1505.             bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
  1506.             AIEnter_Stand(bs);
  1507.             return qfalse;
  1508.         }
  1509.     }
  1510.     //if the enemy is not visible
  1511.     if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
  1512.         if (BotWantsToChase(bs)) {
  1513.             AIEnter_Battle_Chase(bs);
  1514.             return qfalse;
  1515.         }
  1516.         else {
  1517.             AIEnter_Seek_LTG(bs);
  1518.             return qfalse;
  1519.         }
  1520.     }
  1521.     //use holdable items
  1522.     BotBattleUseItems(bs);
  1523.     //
  1524.     bs->tfl = TFL_DEFAULT;
  1525.     if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
  1526.     //if in lava or slime the bot should be able to get out
  1527.     if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
  1528.     //
  1529.     if (BotCanAndWantsToRocketJump(bs)) {
  1530.         bs->tfl |= TFL_ROCKETJUMP;
  1531.     }
  1532.     //choose the best weapon to fight with
  1533.     BotChooseWeapon(bs);
  1534.     //do attack movements
  1535.     moveresult = BotAttackMove(bs, bs->tfl);
  1536.     //if the movement failed
  1537.     if (moveresult.failure) {
  1538.         //reset the avoid reach, otherwise bot is stuck in current area
  1539.         trap_BotResetAvoidReach(bs->ms);
  1540.         //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
  1541.         bs->ltg_time = 0;
  1542.     }
  1543.     //
  1544.     BotAIBlocked(bs, &moveresult, qfalse);
  1545.     //aim at the enemy
  1546.     BotAimAtEnemy(bs);
  1547.     //attack the enemy if possible
  1548.     BotCheckAttack(bs);
  1549.     //if the bot wants to retreat
  1550.     if (!(bs->flags & BFL_FIGHTSUICIDAL)) {
  1551.         if (BotWantsToRetreat(bs)) {
  1552.             AIEnter_Battle_Retreat(bs);
  1553.             return qtrue;
  1554.         }
  1555.     }
  1556.     return qtrue;
  1557. }
  1558.  
  1559. /*
  1560. ==================
  1561. AIEnter_Battle_Chase
  1562. ==================
  1563. */
  1564. void AIEnter_Battle_Chase(bot_state_t *bs) {
  1565.     BotRecordNodeSwitch(bs, "battle chase", "");
  1566.     bs->chase_time = trap_AAS_Time();
  1567.     bs->ainode = AINode_Battle_Chase;
  1568. }
  1569.  
  1570. /*
  1571. ==================
  1572. AINode_Battle_Chase
  1573. ==================
  1574. */
  1575. int AINode_Battle_Chase(bot_state_t *bs)
  1576. {
  1577.     bot_goal_t goal;
  1578.     vec3_t target, dir;
  1579.     bot_moveresult_t moveresult;
  1580.     float range;
  1581.  
  1582.     if (BotIsObserver(bs)) {
  1583.         AIEnter_Observer(bs);
  1584.         return qfalse;
  1585.     }
  1586.     //if in the intermission
  1587.     if (BotIntermission(bs)) {
  1588.         AIEnter_Intermission(bs);
  1589.         return qfalse;
  1590.     }
  1591.     //respawn if dead
  1592.     if (BotIsDead(bs)) {
  1593.         AIEnter_Respawn(bs);
  1594.         return qfalse;
  1595.     }
  1596.     //if no enemy
  1597.     if (bs->enemy < 0) {
  1598.         AIEnter_Seek_LTG(bs);
  1599.         return qfalse;
  1600.     }
  1601.     //if the enemy is visible
  1602.     if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
  1603.         AIEnter_Battle_Fight(bs);
  1604.         return qfalse;
  1605.     }
  1606.     //if there is another enemy
  1607.     if (BotFindEnemy(bs, -1)) {
  1608.         AIEnter_Battle_Fight(bs);
  1609.         return qfalse;
  1610.     }
  1611.     //there is no last enemy area
  1612.     if (!bs->lastenemyareanum) {
  1613.         AIEnter_Seek_LTG(bs);
  1614.         return qfalse;
  1615.     }
  1616.     //
  1617.     bs->tfl = TFL_DEFAULT;
  1618.     if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
  1619.     //if in lava or slime the bot should be able to get out
  1620.     if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
  1621.     //
  1622.     if (BotCanAndWantsToRocketJump(bs)) {
  1623.         bs->tfl |= TFL_ROCKETJUMP;
  1624.     }
  1625.     //map specific code
  1626.     BotMapScripts(bs);
  1627.     //create the chase goal
  1628.     goal.entitynum = bs->enemy;
  1629.     goal.areanum = bs->lastenemyareanum;
  1630.     VectorCopy(bs->lastenemyorigin, goal.origin);
  1631.     VectorSet(goal.mins, -8, -8, -8);
  1632.     VectorSet(goal.maxs, 8, 8, 8);
  1633.     //if the last seen enemy spot is reached the enemy could not be found
  1634.     if (trap_BotTouchingGoal(bs->origin, &goal)) bs->chase_time = 0;
  1635.     //if there's no chase time left
  1636.     if (!bs->chase_time || bs->chase_time < trap_AAS_Time() - 10) {
  1637.         AIEnter_Seek_LTG(bs);
  1638.         return qfalse;
  1639.     }
  1640.     //check for nearby goals periodicly
  1641.     if (bs->check_time < trap_AAS_Time()) {
  1642.         bs->check_time = trap_AAS_Time() + 1;
  1643.         range = 150;
  1644.         //
  1645.         if (BotNearbyGoal(bs, bs->tfl, &goal, range)) {
  1646.             //the bot gets 5 seconds to pick up the nearby goal item
  1647.             bs->nbg_time = trap_AAS_Time() + 0.1 * range + 1;
  1648.             trap_BotResetLastAvoidReach(bs->ms);
  1649.             AIEnter_Battle_NBG(bs);
  1650.             return qfalse;
  1651.         }
  1652.     }
  1653.     //
  1654.     BotUpdateBattleInventory(bs, bs->enemy);
  1655.     //initialize the movement state
  1656.     BotSetupForMovement(bs);
  1657.     //move towards the goal
  1658.     trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
  1659.     //if the movement failed
  1660.     if (moveresult.failure) {
  1661.         //reset the avoid reach, otherwise bot is stuck in current area
  1662.         trap_BotResetAvoidReach(bs->ms);
  1663.         //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
  1664.         bs->ltg_time = 0;
  1665.     }
  1666.     //
  1667.     BotAIBlocked(bs, &moveresult, qfalse);
  1668.     //
  1669.     if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
  1670.         VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
  1671.     }
  1672.     else if (!(bs->flags & BFL_IDEALVIEWSET)) {
  1673.         if (bs->chase_time > trap_AAS_Time() - 2) {
  1674.             BotAimAtEnemy(bs);
  1675.         }
  1676.         else {
  1677.             if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
  1678.                 VectorSubtract(target, bs->origin, dir);
  1679.                 vectoangles(dir, bs->ideal_viewangles);
  1680.             }
  1681.             else {
  1682.                 vectoangles(moveresult.movedir, bs->ideal_viewangles);
  1683.             }
  1684.         }
  1685.         bs->ideal_viewangles[2] *= 0.5;
  1686.     }
  1687.     //if the weapon is used for the bot movement
  1688.     if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
  1689.     //if the bot is in the area the enemy was last seen in
  1690.     if (bs->areanum == bs->lastenemyareanum) bs->chase_time = 0;
  1691.     //if the bot wants to retreat (the bot could have been damage during the chase)
  1692.     if (BotWantsToRetreat(bs)) {
  1693.         AIEnter_Battle_Retreat(bs);
  1694.         return qtrue;
  1695.     }
  1696.     return qtrue;
  1697. }
  1698.  
  1699. /*
  1700. ==================
  1701. AIEnter_Battle_Retreat
  1702. ==================
  1703. */
  1704. void AIEnter_Battle_Retreat(bot_state_t *bs) {
  1705.     BotRecordNodeSwitch(bs, "battle retreat", "");
  1706.     bs->ainode = AINode_Battle_Retreat;
  1707. }
  1708.  
  1709. /*
  1710. ==================
  1711. AINode_Battle_Retreat
  1712. ==================
  1713. */
  1714. int AINode_Battle_Retreat(bot_state_t *bs) {
  1715.     bot_goal_t goal;
  1716.     aas_entityinfo_t entinfo;
  1717.     bot_moveresult_t moveresult;
  1718.     vec3_t target, dir;
  1719.     float attack_skill, range;
  1720.     int areanum;
  1721.  
  1722.     if (BotIsObserver(bs)) {
  1723.         AIEnter_Observer(bs);
  1724.         return qfalse;
  1725.     }
  1726.     //if in the intermission
  1727.     if (BotIntermission(bs)) {
  1728.         AIEnter_Intermission(bs);
  1729.         return qfalse;
  1730.     }
  1731.     //respawn if dead
  1732.     if (BotIsDead(bs)) {
  1733.         AIEnter_Respawn(bs);
  1734.         return qfalse;
  1735.     }
  1736.     //if no enemy
  1737.     if (bs->enemy < 0) {
  1738.         AIEnter_Seek_LTG(bs);
  1739.         return qfalse;
  1740.     }
  1741.     //
  1742.     BotEntityInfo(bs->enemy, &entinfo);
  1743.     if (EntityIsDead(&entinfo)) {
  1744.         AIEnter_Seek_LTG(bs);
  1745.         return qfalse;
  1746.     }
  1747.     //
  1748.     bs->tfl = TFL_DEFAULT;
  1749.     if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
  1750.     //if in lava or slime the bot should be able to get out
  1751.     if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
  1752.     //map specific code
  1753.     BotMapScripts(bs);
  1754.     //update the attack inventory values
  1755.     BotUpdateBattleInventory(bs, bs->enemy);
  1756.     //if the bot doesn't want to retreat anymore... probably picked up some nice items
  1757.     if (BotWantsToChase(bs)) {
  1758.         //empty the goal stack, when chasing, only the enemy is the goal
  1759.         trap_BotEmptyGoalStack(bs->gs);
  1760.         //go chase the enemy
  1761.         AIEnter_Battle_Chase(bs);
  1762.         return qfalse;
  1763.     }
  1764.     //update the last time the enemy was visible
  1765.     if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
  1766.         bs->enemyvisible_time = trap_AAS_Time();
  1767.         //update the reachability area and origin if possible
  1768.         areanum = BotPointAreaNum(entinfo.origin);
  1769.         if (areanum && trap_AAS_AreaReachability(areanum)) {
  1770.             VectorCopy(entinfo.origin, bs->lastenemyorigin);
  1771.             bs->lastenemyareanum = areanum;
  1772.         }
  1773.     }
  1774.     //if the enemy is NOT visible for 4 seconds
  1775.     if (bs->enemyvisible_time < trap_AAS_Time() - 4) {
  1776.         AIEnter_Seek_LTG(bs);
  1777.         return qfalse;
  1778.     }
  1779.     //else if the enemy is NOT visible
  1780.     else if (bs->enemyvisible_time < trap_AAS_Time()) {
  1781.         //if there is another enemy
  1782.         if (BotFindEnemy(bs, -1)) {
  1783.             AIEnter_Battle_Fight(bs);
  1784.             return qfalse;
  1785.         }
  1786.     }
  1787.     //
  1788. #ifdef CTF
  1789.     if (gametype == GT_CTF) {
  1790.         BotCTFRetreatGoals(bs);
  1791.     }
  1792. #endif //CTF
  1793.     //use holdable items
  1794.     BotBattleUseItems(bs);
  1795.     //get the current long term goal while retreating
  1796.     if (!BotLongTermGoal(bs, bs->tfl, qtrue, &goal)) {
  1797.         AIEnter_Battle_SuicidalFight(bs);
  1798.         return qfalse;
  1799.     }
  1800.     //check for nearby goals periodicly
  1801.     if (bs->check_time < trap_AAS_Time()) {
  1802.         bs->check_time = trap_AAS_Time() + 1;
  1803.         range = 150;
  1804. #ifdef CTF
  1805.         //if carrying a flag the bot shouldn't be distracted too much
  1806.         if (BotCTFCarryingFlag(bs)) range = 100;
  1807. #endif //CTF
  1808.         //
  1809.         if (BotNearbyGoal(bs, bs->tfl, &goal, range)) {
  1810.             trap_BotResetLastAvoidReach(bs->ms);
  1811.             //time the bot gets to pick up the nearby goal item
  1812.             bs->nbg_time = trap_AAS_Time() + range / 100 + 1;
  1813.             AIEnter_Battle_NBG(bs);
  1814.             return qfalse;
  1815.         }
  1816.     }
  1817.     //initialize the movement state
  1818.     BotSetupForMovement(bs);
  1819.     //move towards the goal
  1820.     trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
  1821.     //if the movement failed
  1822.     if (moveresult.failure) {
  1823.         //reset the avoid reach, otherwise bot is stuck in current area
  1824.         trap_BotResetAvoidReach(bs->ms);
  1825.         //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
  1826.         bs->ltg_time = 0;
  1827.     }
  1828.     //
  1829.     BotAIBlocked(bs, &moveresult, qfalse);
  1830.     //choose the best weapon to fight with
  1831.     BotChooseWeapon(bs);
  1832.     //if the view is fixed for the movement
  1833.     if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
  1834.         VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
  1835.     }
  1836.     else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET)
  1837.                 && !(bs->flags & BFL_IDEALVIEWSET) ) {
  1838.         attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1);
  1839.         //if the bot is skilled anough
  1840.         if (attack_skill > 0.3) {
  1841.             BotAimAtEnemy(bs);
  1842.         }
  1843.         else {
  1844.             if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
  1845.                 VectorSubtract(target, bs->origin, dir);
  1846.                 vectoangles(dir, bs->ideal_viewangles);
  1847.             }
  1848.             else {
  1849.                 vectoangles(moveresult.movedir, bs->ideal_viewangles);
  1850.             }
  1851.             bs->ideal_viewangles[2] *= 0.5;
  1852.         }
  1853.     }
  1854.     //if the weapon is used for the bot movement
  1855.     if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
  1856.     //attack the enemy if possible
  1857.     BotCheckAttack(bs);
  1858.     //
  1859.     return qtrue;
  1860. }
  1861.  
  1862. /*
  1863. ==================
  1864. AIEnter_Battle_NBG
  1865. ==================
  1866. */
  1867. void AIEnter_Battle_NBG(bot_state_t *bs) {
  1868.     BotRecordNodeSwitch(bs, "battle NBG", "");
  1869.     bs->ainode = AINode_Battle_NBG;
  1870. }
  1871.  
  1872. /*
  1873. ==================
  1874. AINode_Battle_NBG
  1875. ==================
  1876. */
  1877. int AINode_Battle_NBG(bot_state_t *bs) {
  1878.     int areanum;
  1879.     bot_goal_t goal;
  1880.     aas_entityinfo_t entinfo;
  1881.     bot_moveresult_t moveresult;
  1882.     float attack_skill;
  1883.     vec3_t target, dir;
  1884.  
  1885.     if (BotIsObserver(bs)) {
  1886.         AIEnter_Observer(bs);
  1887.         return qfalse;
  1888.     }
  1889.     //if in the intermission
  1890.     if (BotIntermission(bs)) {
  1891.         AIEnter_Intermission(bs);
  1892.         return qfalse;
  1893.     }
  1894.     //respawn if dead
  1895.     if (BotIsDead(bs)) {
  1896.         AIEnter_Respawn(bs);
  1897.         return qfalse;
  1898.     }
  1899.     //if no enemy
  1900.     if (bs->enemy < 0) {
  1901.         AIEnter_Seek_NBG(bs);
  1902.         return qfalse;
  1903.     }
  1904.     //
  1905.     BotEntityInfo(bs->enemy, &entinfo);
  1906.     if (EntityIsDead(&entinfo)) {
  1907.         AIEnter_Seek_NBG(bs);
  1908.         return qfalse;
  1909.     }
  1910.     //
  1911.     bs->tfl = TFL_DEFAULT;
  1912.     if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
  1913.     //if in lava or slime the bot should be able to get out
  1914.     if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
  1915.     //
  1916.     if (BotCanAndWantsToRocketJump(bs)) {
  1917.         bs->tfl |= TFL_ROCKETJUMP;
  1918.     }
  1919.     //map specific code
  1920.     BotMapScripts(bs);
  1921.     //update the last time the enemy was visible
  1922.     if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
  1923.         bs->enemyvisible_time = trap_AAS_Time();
  1924.         //update the reachability area and origin if possible
  1925.         areanum = BotPointAreaNum(entinfo.origin);
  1926.         if (areanum && trap_AAS_AreaReachability(areanum)) {
  1927.             VectorCopy(entinfo.origin, bs->lastenemyorigin);
  1928.             bs->lastenemyareanum = areanum;
  1929.         }
  1930.     }
  1931.     //if the bot has no goal or touches the current goal
  1932.     if (!trap_BotGetTopGoal(bs->gs, &goal)) {
  1933.         bs->nbg_time = 0;
  1934.     }
  1935.     else if (trap_BotTouchingGoal(bs->origin, &goal)) {
  1936.         bs->nbg_time = 0;
  1937.     }
  1938.     //
  1939.     if (bs->nbg_time < trap_AAS_Time()) {
  1940.         //pop the current goal from the stack
  1941.         trap_BotPopGoal(bs->gs);
  1942.         //if the bot still has a goal
  1943.         if (trap_BotGetTopGoal(bs->gs, &goal)) AIEnter_Battle_Retreat(bs);
  1944.         else AIEnter_Battle_Fight(bs);
  1945.         //
  1946.         return qfalse;
  1947.     }
  1948.     //initialize the movement state
  1949.     BotSetupForMovement(bs);
  1950.     //move towards the goal
  1951.     trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
  1952.     //if the movement failed
  1953.     if (moveresult.failure) {
  1954.         //reset the avoid reach, otherwise bot is stuck in current area
  1955.         trap_BotResetAvoidReach(bs->ms);
  1956.         //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
  1957.         bs->nbg_time = 0;
  1958.     }
  1959.     //
  1960.     BotAIBlocked(bs, &moveresult, qfalse);
  1961.     //update the attack inventory values
  1962.     BotUpdateBattleInventory(bs, bs->enemy);
  1963.     //choose the best weapon to fight with
  1964.     BotChooseWeapon(bs);
  1965.     //if the view is fixed for the movement
  1966.     if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
  1967.         VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
  1968.     }
  1969.     else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET)
  1970.                 && !(bs->flags & BFL_IDEALVIEWSET)) {
  1971.         attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1);
  1972.         //if the bot is skilled anough and the enemy is visible
  1973.         if (attack_skill > 0.3) {
  1974.             //&& BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)
  1975.             BotAimAtEnemy(bs);
  1976.         }
  1977.         else {
  1978.             if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
  1979.                 VectorSubtract(target, bs->origin, dir);
  1980.                 vectoangles(dir, bs->ideal_viewangles);
  1981.             }
  1982.             else {
  1983.                 vectoangles(moveresult.movedir, bs->ideal_viewangles);
  1984.             }
  1985.             bs->ideal_viewangles[2] *= 0.5;
  1986.         }
  1987.     }
  1988.     //if the weapon is used for the bot movement
  1989.     if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
  1990.     //attack the enemy if possible
  1991.     BotCheckAttack(bs);
  1992.     //
  1993.     return qtrue;
  1994. }
  1995.  
  1996.